package com.ost.kautilya.modeling.weka;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.reflections.Reflections;

import weka.classifiers.Classifier;
import weka.clusterers.Clusterer;
import weka.core.Instances;
import weka.core.OptionHandler;
import weka.core.converters.ArffSaver;

import com.ost.kautilya.modeling.MProvider;
import com.ost.kautilya.modeling.Model;
import com.ost.kautilya.modeling.Model.ModelType;
import com.ost.kautilya.modeling.ModelConfiguration;
import com.ost.kautilya.modeling.ModelMetaData;
import com.ost.kautilya.modeling.ModelParameters;

public class WekaModelingProvider implements MProvider {

	private static Reflections ref = new MReflections("weka");

	private static List<String> availableClassifiers = new ArrayList<String>();
	private static List<String> availableClusterers = new ArrayList<String>();
	static {
		try {
			Set<Class<? extends Classifier>> clf = new HashSet<Class<? extends Classifier>>();
			clf.addAll(ref.getSubTypesOf(Classifier.class));
			for (Class<? extends Classifier> cl : clf) {
				try {
					if (!cl.isEnum() && !cl.isInterface() && !Modifier.isAbstract(cl.getModifiers()) && cl.getConstructor() != null)
						availableClassifiers.add(cl.getName());
				} catch (Exception e) {
				}
			}
			Collections.sort(availableClassifiers);
		} catch (Exception e) {
			e.printStackTrace();
		}
		try {
			Set<Class<? extends Clusterer>> clf = ref.getSubTypesOf(Clusterer.class);
			for (Class<? extends Clusterer> cl : clf) {
				try {
					if (!cl.isEnum() && !cl.isInterface() && !Modifier.isAbstract(cl.getModifiers()) && cl.getConstructor() != null)
						availableClusterers.add(cl.getName());
				} catch (Exception e) {
				}
			}
			Collections.sort(availableClusterers);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean isMultiClassSupported(ModelType type) {
		return type == ModelType.FORECASTING;
	}

	@Override
	public List<String> getAvailableParameterValues(int param) {
		switch (param) {
		case ModelParameters.CLUSTERER:
			return availableClusterers;
		case ModelParameters.CLASSIFIER:
			return availableClassifiers;
		default:
			return new ArrayList<String>();
		}
	}

	@Override
	public boolean isSupported(ModelType type) {
		return type != ModelType.CUSTOM;
	}

	@Override
	public Model createRawModel(ModelMetaData meta) throws Exception {
		Model m = null;
		switch (meta.getType()) {
		case CLASSIFICATION:
			m = new WekaClassifierModel(Class.forName(meta.getParameter(ModelParameters.CLASSIFIER)));
			break;
		case CLUSTERING:
			m = new WekaClusteringModel(Class.forName(meta.getParameter(ModelParameters.CLUSTERER)));
			break;
		case FORECASTING:
			m = new WekaTSForcastingModel(Class.forName(meta.getParameter(ModelParameters.CLASSIFIER)));
			break;
		default:
			throw new Exception("Model Type " + meta.getType() + " is not yet supported by " + this.getClass().getName());
		}
		m.setMetaData(meta);
		m.initTransients();
		return m;
	}

	@Override
	public ModelConfiguration getDefaultConfiguration(ModelMetaData meta) {
		ModelConfiguration m = null;
		try {
			switch (meta.getType()) {
			case CLASSIFICATION:
			case FORECASTING:
				m = new WekaOptions((OptionHandler) (Class.forName(meta.getParameter(ModelParameters.CLASSIFIER)).newInstance()));
				break;
			case CLUSTERING:
				m = new WekaOptions((OptionHandler) (Class.forName(meta.getParameter(ModelParameters.CLUSTERER)).newInstance()));
				break;
			default:
				throw new Exception("Model Type " + meta.getType() + " is not yet supported by " + this.getClass().getName());
			}
		} catch (Exception e) {
		}
		return m;
	}

	@Override
	public Map<Integer, String> getDefaultParameters(ModelType type) {
		Map<Integer, String> params = new Hashtable<Integer, String>();
		switch (type) {
		case FORECASTING:
			params.put(ModelParameters.CLASSIFIER, "weka.classifiers.functions.LinearRegression");
			break;
		default:
			break;
		}
		return params;
	}
	
	@Override
	public String getDataExportFormat() {
		return "TEXT/ARFF";
	}
	
	@Override
	public void dumpData(Serializable data, String format, OutputStream out) throws IOException {
		if (format.equalsIgnoreCase("text/arff")) {
			ArffSaver saver = new ArffSaver();
			saver.setInstances((Instances) data);
			saver.setDestination(out);
			saver.writeBatch();
		}
	}
}
